%% Copyright (c) 2008, Yariv Sadan, yarivsblog@gmail.com
%% All rights reserved.
%%
%% Redistribution and use in source and binary forms, with or without 
%% modification, are permitted provided that the following conditions are met:
%%
%%     * Redistributions of source code must retain the above copyright 
%%       notice, this list of conditions and the following disclaimer.
%%     * Redistributions in binary form must reproduce the above copyright 
%%       notice, this list of conditions and the following disclaimer in the 
%%       documentation and/or other materials provided with the distribution.
%%     * Neither the name of "Erlang Friendfeed API" nor the names of its
%%       contributors may be 
%%       used to endorse or promote products derived from this software
%%       without specific prior written permission.
%%
%% THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" 
%% AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE 
%% IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE 
%% ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE 
%% LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR 
%% CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF 
%% SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS 
%% INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN 
%% CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) 
%% ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE 
%% POSSIBILITY OF SUCH DAMAGE.

%% @author Yariv Sadan (yarivsblog@gmail.com)
%%
%% @doc This module implements the FriendFeed API. It's not documented yet,
%% it's incomplete, and it's definitely alpha quality.

-module(friendfeed).
-export([feed_public/0,
	 feed_user/1,
	 feed_user_comments/1,
	 feed_user_likes/1,
	 feed_user_discussion/1,
	 feed_multi_user/1,
	 feed_home/1,
	 feed_search/1,
	 publish/2,
	 publish/3,
	 publish/4,
	 publish/5,
	 comment/3,
	 comment/4,
	 delete_comment/3,
	 like/2,
	 delete_like/2]).
	 

feed_public() ->
    do_get("feed/public").

feed_user(Nickname) ->
    do_get("feed/user/" ++ Nickname).

feed_user_comments(Nickname) ->
    do_get("feed/user/" ++ Nickname ++ "/comments").

feed_user_likes(Nickname) ->
    do_get("feed/user/" ++ Nickname ++ "/likes").

feed_user_discussion(Nickname) ->
    do_get("feed/user/" ++ Nickname ++ "/discussion").

feed_multi_user(Nicknames) ->
    do_get("feed/user?nickname=" ++ join(Nicknames, ",")).

feed_home(FF) ->
    do_get("feed/home", FF).

feed_search(Query) ->
    do_get("feed/search?q=" ++ url_encode(Query)).

publish(FF, Title) ->
    publish(FF, Title, undefined).

publish(FF, Title, Link) ->
    publish(FF, Title, Link, undefined).

publish(FF, Title, Link, Comment) ->
    publish(FF, Title, Link, Comment, []).

publish(FF, Title, Link, Comment, Images) ->
    Elems = [{"title", Title},
	     {"link", Link},
	     {"comment", Comment}],
    {_Idx, Images1} =
	lists:foldl(
	  fun({Url, ImgLink}, {N, Acc}) ->
		  {N+1,
		   [{"image" ++ integer_to_list(N) ++ "_url", Url},
		    {"image" ++ integer_to_list(N) ++ "_link", ImgLink} |
		    Acc]};
	     (Url, {N, Acc}) ->
		  {N+1,
		   [{"image" ++ integer_to_list(N) ++ "_url", Url} |
		    Acc]}
	  end, {0, []}, Images), 
    do_post("share", FF, Elems ++ Images1).

comment(FF, Entry, Body) ->
    comment(FF, Entry, Body, undefined).

comment(FF, Entry, Body, Comment) ->
    do_post("feed/comment",
	    FF,
	    [{"entry", Entry},
	     {"body", Body},
	     {"comment", Comment}]).

delete_comment(FF, Entry, Comment) ->
    do_post("feed/comment/delete",
	    FF,
	    [{"entry", Entry},
	     {"comment", Comment}]).

like(FF, Entry) ->
    do_post("feed/like",
	    FF,
	    [{"entry", Entry}]).

delete_like(FF, Entry) ->
    do_post("feed/like/delete",
	    FF,
	    [{"entry", Entry}]).
	    
do_get(Url) ->
    do_get(Url, undefined).

do_get(Url, FF) ->
    req(Url, FF, get).

do_post(Url, FF, Params) ->
    Body = 
	lists:foldl(
	  fun({_Key, undefined}, Acc) -> Acc;
	     ({Key, Val}, Acc) ->
		  [[Key, $=, url_encode(Val)] | Acc]
	  end, [], Params),
    Body1 = lists:flatten(join(Body, $&)),
    req(Url, FF, {post, Body1}).

req(Url, FF, Method) ->
    Headers = case FF of
		  undefined -> [];
		  {Nickname, Key} ->
		      [{"Authorization", "Basic " ++
			binary_to_list(base64:encode(Nickname ++ ":" ++ Key))}]
	      end,
    {Method1, Req} =
	case Method of
	    get ->
		{get, {get_url(Url), Headers}};
	    {post, Body} ->
		{post, {get_url(Url),
			Headers, "application/x-www-form-urlencoded", Body}}
	end,
    case http:request(Method1, Req, [], []) of
	{ok, {{_Protocol, Status, _Msg}, _Headers, Resp}} ->
	    case Status of
		200 -> json:decode_string(Resp);
		_Other -> exit({unexpected_status_code, Status})
	    end;
	Err ->
	    exit(Err)
    end.

get_url(Url) ->
    "http://friendfeed.com/api/" ++ Url.

join(List, Sep) ->
    lists:foldr(fun(X, []) ->
			[X];
		   (X, Acc) ->
			[X, Sep | Acc]
		end, [], List).


%% The code below was taken from Yaws

url_encode([H|T]) ->
    if
	H >= $a, $z >= H ->
	    [H|url_encode(T)];
	H >= $A, $Z >= H ->
	    [H|url_encode(T)];
	H >= $0, $9 >= H ->
	    [H|url_encode(T)];
	H == $_; H == $.; H == $-; H == $/; H == $: -> % FIXME: more..
	    [H|url_encode(T)];
	H == 32 ->
	    [$+|url_encode(T)];
	true ->
	    case integer_to_hex(H) of
		[X, Y] ->
		    [$%, X, Y | url_encode(T)];
		[X] ->
		    [$%, $0, X | url_encode(T)]
	    end
    end;

url_encode([]) ->
    [].

integer_to_hex(I) ->
    case catch erlang:integer_to_list(I, 16) of
	{'EXIT', _} ->
	    old_integer_to_hex(I);
	Int ->
	    Int
    end.


old_integer_to_hex(I) when I<10 ->
    integer_to_list(I);
old_integer_to_hex(I) when I<16 ->
    [I-10+$A];
old_integer_to_hex(I) when I>=16 ->
    N = trunc(I/16),
    old_integer_to_hex(N) ++ old_integer_to_hex(I rem 16).





